﻿namespace PugLib.Collections
{
	using System.Collections.Generic;
	using System.Collections.ObjectModel;

	using Extensions.Collections;

	/// <summary>
	/// A simple, but slow data structure that filters out duplicate items automatically.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class HashTally<T>
	{
		private readonly Dictionary<int, long> _count;
		private readonly List<T> _items;
		private readonly HashSet<int> _set;

		public HashTally()
		{
			_set = new HashSet<int>();
			_items = new List<T>();
			_count = new Dictionary<int, long>();
		}

		public int TotalCount
		{
			get { return _items.Count; }
		}

		public void Reset()
		{
			_set.Clear();
			_items.Clear();
			_count.Clear();
		}

		public void Add(T item)
		{
			int hash = item.GetHashCode();
			lock (_count)
			{
				lock (_set)
				{
					if (_set.Contains(hash))
					{
						_count[hash]++;
					}
					else
					{
						_set.Add(hash);
						_count.Add(hash, 1);
					}
				}
				lock (_items)
				{
					_items.Add(item);
				}
			}
		}

		public long GetTallyForItem(T item)
		{
			int hash = item.GetHashCode();
			return (_set.Contains(hash)) ? _count[hash] : 0;
		}

		public ReadOnlyCollection<T> GetAllItemsWithTallyGreaterThan(long minimum)
		{
			List<T> rtn = new List<T>();
			Dictionary<int, long> snapshotCopyOfCount;
			List<T> snapshotCopyOfItems;
			lock (_count)
			{
				snapshotCopyOfCount = new Dictionary<int, long>(_count);
			}
			lock (_items)
			{
				snapshotCopyOfItems = new List<T>(_items);
			}

			snapshotCopyOfCount.Keys.ForEach(hash =>
			                                 {
			                                 	if (_count[hash] > minimum)
			                                 	{
			                                 		List<T> moreItems = snapshotCopyOfItems.FindAll(foo => (foo.GetHashCode() == hash));
			                                 		moreItems.ForEach(item =>
			                                 		                  {
			                                 		                  	if (!rtn.Contains(item))
			                                 		                  	{
			                                 		                  		rtn.Add(item);
			                                 		                  	}
			                                 		                  });
			                                 	}
			                                 });
			return rtn.AsReadOnly();
		}
	}
}